//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics.Contracts;
using OpenLS.Core.Serialization;

namespace OpenLS.Spreadsheet
{
    public class DynamicFilter: Filter
    {
        internal override void ReadXmlCore(ReadContext context)
        {
            using (ReadContext c = context.Read(xmlName))
            {
                Type = c.GetEnum<DynamicFilterType>("type");
                Value = c.GetOptionalDouble("val");
                MaxValue = c.GetOptionalDouble("maxVal");
            }
        }
        const string xmlName = "dynamicFilter";

        internal override void WriteXmlCore(WriteContext context)
        {
            using (WriteContext c = context.Write(xmlName))
            {
                c.SetEnum("type", Type);
                c.SetOptionalDouble("val", Value);
                c.SetOptionalDouble("maxVal", MaxValue);
            }
        }

        private DynamicFilterType _type = DynamicFilterType.Null;

        public DynamicFilterType Type
        {
            get { return _type; }
            set { _type = value; }
        }

        private double? _val;

        public double? Value
        {
            get { return _val; }
            set { _val = value; }
        }
        private double? _maxValue;

        public double? MaxValue
        {
            get { return _maxValue; }
            set { _maxValue = value; }
        }



        internal override bool GetValue(Cell cell, RangeAddress rangeAddress)
        {
            Contract.Assert(false, "to implement");
            return true; //\\ for now!!
        }
        internal override Func<Cell, bool> GetCellEvaluator(Range range)
        {
            switch (Type)
            {
                case DynamicFilterType.AboveAverage:
                    {
                        CellValueDistribution distribution = new CellValueDistribution(range);
                        return c => c.Value is double && (double) c.Value >=
                                    distribution.Average;
                    }
                case DynamicFilterType.BelowAverage:
                    {
                        CellValueDistribution distribution = new CellValueDistribution(range);
                        return c => c.Value is double && (double) c.Value <=
                                    distribution.Average;
                    }
                    case DynamicFilterType.LastMonth:
                    return c => IsDate(c, IsLastMonth);
                    case DynamicFilterType.LastQuarter:
                    return c => IsDate(c, IsLastQuarter);
                    case DynamicFilterType.LastWeek:
                    return c => IsDate(c, IsLastWeek);
                    case DynamicFilterType.LastYear:
                    return c => IsDate(c, IsLastYear);
                    case DynamicFilterType.M1:
                    return c => IsDate(c, d => IsMonth(d, 1));
                    case DynamicFilterType.M10:
                    return c => IsDate(c, d => IsMonth(d, 10));
                    case DynamicFilterType.M11:
                    return c => IsDate(c, d => IsMonth(d, 11));

                    case DynamicFilterType.M12: return c => IsDate(c, d => IsMonth(d, 12));

                    case DynamicFilterType.M2: return c => IsDate(c, d => IsMonth(d, 2));

                    case DynamicFilterType.M3: return c => IsDate(c, d => IsMonth(d, 3));

                    case DynamicFilterType.M4: return c => IsDate(c, d => IsMonth(d, 4));

                    case DynamicFilterType.M5: return c => IsDate(c, d => IsMonth(d, 5));

                    case DynamicFilterType.M6: return c => IsDate(c, d => IsMonth(d, 6));

                    case DynamicFilterType.M7: return c => IsDate(c, d => IsMonth(d, 7));

                    case DynamicFilterType.M8: return c => IsDate(c, d => IsMonth(d, 8));

                    case DynamicFilterType.M9: return c => IsDate(c, d => IsMonth(d, 9));

                    case DynamicFilterType.NextMonth:
                    case DynamicFilterType.NextQuarter:
                    case DynamicFilterType.NextWeek:
                    case DynamicFilterType.NextYear:
                case DynamicFilterType.Null:
                case DynamicFilterType.Q1: return c => IsDate(c, d => IsQuarter(d, 1));

                case DynamicFilterType.Q2: return c => IsDate(c, d => IsQuarter(d, 2));
                case DynamicFilterType.Q3: return c => IsDate(c, d => IsQuarter(d, 3));
                case DynamicFilterType.Q4: return c => IsDate(c, d => IsQuarter(d, 4));
                case DynamicFilterType.ThisMonth:
                    return c => IsDate(c, IsThisMonth);
                    case DynamicFilterType.ThisQuarter:
                    return c => IsDate(c, IsThisQuarter);
                    case DynamicFilterType.ThisWeek:
                    return c => IsDate(c, IsThisWeek);

                    case DynamicFilterType.ThisYear:
                    return c => IsDate(c, IsThisYear);

                    case DynamicFilterType.Today:
                    return c => IsDate(c, IsToday);

                    case DynamicFilterType.Tomorrow:
                    return c => IsDate(c, IsTomorrow);

                    case DynamicFilterType.YearToDate:
                    return c => IsDate(c, IsYearToDate);

                    case DynamicFilterType.Yesterday:
                    return c => IsDate(c, IsYesterday);

                    

            }
            return c => true;
        }

        private bool IsYesterday(DateTime arg)
        {
            return arg.AddDays(1).Date == DateTime.Today;
        }

        private bool IsYearToDate(DateTime arg)
        {
            var today = DateTime.Today;
            var lastYear = today.AddYears(-1);
            return arg >= lastYear && arg <= today;
        }

        private bool IsTomorrow(DateTime arg)
        {
            return arg.AddDays(-1).Date == DateTime.Today;
        }

        private bool IsToday(DateTime arg)
        {
            return arg.Date == DateTime.Today;
        }

        private bool IsThisYear(DateTime arg)
        {
            return arg.Year == DateTime.Now.Year;
        }
        const DayOfWeek WeekStartDay = DayOfWeek.Monday;
        private bool IsThisWeek(DateTime arg)
        {
            return StartOfWeek(arg, WeekStartDay).Date == StartOfWeek(DateTime.Today, WeekStartDay);
        }

        private bool IsThisQuarter(DateTime arg)
        {
            return GetQuarter(arg) == GetQuarter(DateTime.Today) && IsThisYear(arg);
        }

        private bool IsThisMonth(DateTime dateTime)
        {
            return dateTime.Month == DateTime.Today.Month && IsThisYear(dateTime);
        }

        private bool IsQuarter(DateTime dateTime, int i)
        {
            return GetQuarter(dateTime) == i;
        }

        private bool IsLastYear(DateTime arg)
        {
            return arg.Year == DateTime.Today.Year - 1;
        }
        public static DateTime StartOfWeek(DateTime dt, DayOfWeek startOfWeek)
        {
            int diff = dt.DayOfWeek - startOfWeek;
            if (diff < 0)
            {
                diff += 7;
            }
            return dt.AddDays(-1*diff).Date;
        }
        public static int GetQuarter (DateTime dt)
        {
            return ( dt.Month + 2)/3;
        }

        private bool IsLastWeek(DateTime arg)
        {
            DateTime thisWeek = StartOfWeek(DateTime.Today, WeekStartDay);
            DateTime argWeek = StartOfWeek(arg, WeekStartDay);
            var diff = (thisWeek - argWeek).Days;
            return diff == 7;
        }

        private bool IsLastQuarter(DateTime arg)
        {
            var today = DateTime.Today;
            if (arg > today)
                return false;
            var qDiff = GetQuarter(today) - GetQuarter(arg) + 4;
            qDiff = qDiff%4;
            if (qDiff != 1)
                return false;
            return (today - arg).Days < 200;

        }

        private bool IsMonth(DateTime dateTime, int i)
        {
            return dateTime.Month == i;
        }

        static bool IsDate(Cell cell, Func< DateTime, bool> f)
        {
            DateTime? ndt = cell.DateValue;
            if (ndt == null)
                return false;
            return f(ndt.Value);
        }
        static bool IsLastMonth(DateTime ndt)
        {
               var dt = ndt.AddMonths(1);
            var today = DateTime.Today;
            return dt.Year == today.Year && dt.Month == today.Month;
        }
    }
}
